home *** CD-ROM | disk | FTP | other *** search
- /*
- Functions for concatenating and merging a list of folders containing
- text files.
-
- Using this program you can merge text files from different folders.
- Files with the same name that occur in more than one folder are
- concatenated together in the order of the time of last modification, and
- the resulting file is then placed in the destination folder. Files that
- occur in only one source folder are essentially copied to the destination
- folder. The original files are not modified.
-
- I wrote this to help me maintain my archives of newsgroups and mail
- from my Unix account. For instance, say I read four newsgroups:
-
- comp.sys.mac.programmer
- rec.backcountry
- rec.bicycles
- sci.med.occupational
-
- I keep archives of interesting articles from each newsgroup. Eventually,
- there's so much on my Unix account, which has a limit of 5Mb, that
- I need to download the archives. The first time this is easy: simply
- download the files and put them in a folder called "News", then delete
- them from the Unix account. However, now I continue reading news
- and archive new messages. Eventually, my account fills up, and I have
- to download the files once again. Now I have the old archives in
- a folder called "News Old", and the new archives are in a folder
- called "News New". Clearly, this process will repeat itself and
- I will end up with many "News XXX" folders. This would make reading
- archived articles very tedious.
-
- Here's an example of how this program might operate on three
- folders named "News Oldest", "News Newer", and "News Newest";
- the contents of each folder are shown in the following table.
-
- News Old News New News Newest
- comp.sys.mac.programmer comp.sys.mac.programmer comp.sys.mac.programmer
- rec.backcountry rec.backcountry
- sci.med.occupational sci.med.occupational
- rec.bicycles
-
- After running this program on the three folders, the contents of
- the output folder "News Out" will be:
-
- comp.sys.mac.programmer
- rec.backcountry
- sci.med.occupational
- rec.bicycles
-
- The file "News Out:comp.sys.mac.programmer:" will consist of the
- result of concatenating the file comp.sys.mac.programmer in each
- of the three source folders. The first file to be concatenated will be
- the file with the earliest modification time, the second file
- to be concatenated will be the file with the next later modification
- time, etc.
-
- The file "News Out:rec.backcountry:" will consist of the result of
- concatenating the files "News Old:rec.backcountry:" and
- "News New:rec.backcountry:". The file "News Out:sci.med.occupational:"
- will consist of the result of concatenating the files
- "News Old:sci.med.occupational:" and "News Newest:sci.med.occupational:".
- Finally, the file "News Out:rec.bicycles:" will be the same as the
- file "News New:rec.bicycles".
-
- After running this program, I will have a single folder containing all
- of my archived messages. I can then read them using a program such
- as Easy View or Eudora. Once I'm satisfied that the files were
- downloaded and merged correctly I can delete the source folders
- and remove the files from my Unix account.
-
- 94/01/19 aih - added description
- 93/10/17 aih - continuing improvements
- 93/10/14 aih - created
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include "pstr.h"
- #include "ConcatenateTextFolders.h"
- #include "DialogLib.h"
- #include "DialogModalLib.h"
- #include "FileDialogLib.h"
- #include "FolderLib.h"
- #include "LowMemLib.h"
- #include "MemoryLib.h"
- #include "ProgressLib.h"
- #include "ResourceConstantsLib.h"
- #include "ResourceLib.h"
- #include "SortLib.h"
- #include "TimeLib.h"
-
- /* concatenate the contents of the source file's data fork to the
- data fork of the destination file */
- static void FileConcatenate(FileType *src, FileType *dst)
- {
- FilePosType count;
- char buf[FILE_BUFSIZ];
-
- TRY {
- FileOpen(src, fsRdPerm);
- FileOpen(dst, fsWrPerm);
- FileSeek(dst, fsFromLEOF, 0);
- FileCopyFork(src, dst);
- } CLEANUP {
- FileClose(src);
- FileClose(dst);
- } ENDTRY;
- }
-
- /*---------------------------------------------------------------------------*/
-
- /* get modification date of file */
- static TimeType FileModDate(FileType *fp)
- {
- CInfoPBRec cat;
-
- FileCatalog(fp, &cat);
- return(cat.hFileInfo.ioFlMdDat);
- }
-
- /*---------------------------------------------------------------------------*/
-
- /* structure used to sort files by modification date */
- typedef struct {
- TimeType mod;
- int index;
- } DateIndexType;
-
- /* data used by action function */
- typedef struct {
- DateIndexType *dates; /* modification dates of source files */
- FileType *src; /* array of folders containing original files */
- FileType *dst; /* destination folder for merged files */
- int nfolders; /* number of folders to merge */
- int index; /* index into array of folders */
- ProgressHandle progress; /* progress dialog */
- } ConcatenateData;
-
- /* function used to compare modification dates of two files during a sort */
- static int CompareFileDates(const void *a, const void *b)
- {
- const DateIndexType *x = a, *y = b;
- if (x->mod < y->mod) return(-1);
- if (x->mod > y->mod) return(1);
- return(0);
- }
-
- /* This function is called by FileScan for every file and folder in the source
- directories. The files in the source directories are concatenated and
- placed in the destination directory. */
- static Boolean ConcatenateFiles(const CInfoPBRec *pb, void *data)
- {
- ConcatenateData *catdata = data;
- FileNameType name; /* current file name */
- Boolean dstexists = false; /* true if destination file exists */
- FileType *src = NULL; /* current source file */
- CStr31 prompt; /* prompt for progress dialog */
- int i = 0;
-
- /* get name of current file */
- p2cstrcpy(name, pb->hFileInfo.ioNamePtr);
-
- /* if the file already exists in the destination folder then it means
- we've already concatenated it while scanning a prior source folder,
- so it can be skipped this time through */
- FileNameSet(catdata->dst, name);
- dstexists = FileExists(catdata->dst);
- if (! dstexists) {
-
- /* get the modification dates of the files with the current file's name in
- each of the source folders, then sort the files by their modification
- dates */
- for (i = catdata->index; i < catdata->nfolders; i++) {
- catdata->dates[i].mod = 0;
- catdata->dates[i].index = i;
- src = &catdata->src[i];
- FileNameSet(src, name);
- if (FileExists(src) && FileTypeGet(src) == 'TEXT')
- catdata->dates[i].mod = FileModDate(src);
- }
- qksort(&catdata->dates[catdata->index], catdata->nfolders - catdata->index,
- sizeof(DateIndexType), CompareFileDates);
-
- /* concatenate text files, starting with the oldest file */
- for (i = catdata->index; i < catdata->nfolders; i++) {
- if (catdata->dates[i].mod) { /* if non-zero then source file exists */
- ResStrLen(RLS_BUSY, RLS_BUSY_CONCATENATING, prompt, sizeof(prompt));
- ProgressPrompt(catdata->progress, prompt, name);
- src = &catdata->src[catdata->dates[i].index];
- if (dstexists)
- FileConcatenate(src, catdata->dst);
- else {
- FileCopy(src, catdata->dst);
- dstexists = true;
- }
- }
- }
- }
-
- /* update progress dialog */
- ProgressRun(catdata->progress, -1);
- return(false);
- }
-
- static void DoConcatenateTextFolders(ConcatenateData *catdata)
- {
- long nfiles = 0; /* number of files to process */
- CStr31 prompt; /* prompt for progress dialog */
- int i = 0;
-
- TRY {
-
- /* setup progress dialog */
- catdata->progress = ProgressBegin(nfiles, NULL, NULL, 0, 0);
-
- /* count number of files to process */
- ResStrLen(RLS_BUSY, RLS_BUSY_COUNTING_FILES, prompt, sizeof(prompt));
- ProgressPrompt(catdata->progress, prompt);
- nfiles = 0;
- for (i = 0; i < catdata->nfolders; i++) {
- nfiles += FolderValence(&catdata->src[i], false);
- ProgressRun(catdata->progress, 0);
- }
- ProgressReset(catdata->progress, nfiles);
-
- /* process each folder */
- for (catdata->index = 0; catdata->index < catdata->nfolders; catdata->index++) {
-
- /* display folder name in progress dialog */
- CInfoPBRec cat;
- Str255 name;
- memset(&cat, 0, sizeof(CInfoPBRec));
- cat.dirInfo.ioVRefNum = catdata->src[catdata->index].vol;
- cat.dirInfo.ioDrDirID = catdata->src[catdata->index].dir;
- cat.dirInfo.ioNamePtr = name;
- cat.dirInfo.ioFDirIndex = -1;
- FailOSErr(PBGetCatInfo(&cat, false));
- ResStrLen(RLS_BUSY, RLS_BUSY_SCANNING, prompt, sizeof(prompt));
- ProgressPrompt(catdata->progress, prompt, p2cstr(name));
-
- /* scan the folder and concatenate files */
- FileNameSet(&catdata->src[catdata->index], "");
- FolderScan(&catdata->src[catdata->index], false,
- ConcatenateFiles, catdata);
- }
-
- } CLEANUP {
- ProgressEnd(catdata->progress);
- catdata->progress = NULL;
- } ENDTRY;
- }
-
- /* filter out non-folders from custom file selection dialog */
- static pascal Boolean FolderFilter(ParamBlockRec *pb, void *data)
- {
- return((pb->fileParam.ioFlAttrib & (1 << kFolderBit)) == 0);
- }
-
- /* Merge the text files contained in the folders listed in
- 'src' into the folder 'dst'. If 'src' is NULL then the user
- is prompted to select a list of folders. If 'dst' is NULL then
- the user is prompted to select a destination folder. */
- void ConcatenateTextFolders(FileType *src, int nfolders, FileType *dst)
- {
- ProgressHandle progress = NULL;/* progress info */
- FileHandle srcfolders = NULL; /* handle to source folders */
- FileType dstfolder; /* destination folder */
- ConcatenateData catdata; /* information needed while merging folders */
- FileNameType name; /* default name of destination folder */
- CInfoPBRec cat; /* for setting folder's modification date */
- FileType svdst; /* destination folder, for setting mod date */
- CStr31 prompt; /* prompt for dialogs */
- int i = 0;
-
- TRY {
-
- /* select source folders */
- if (! src) {
- srcfolders = FileSelectMultipleCustom(FolderFilter,
- NULL, NULL, NULL, NULL, NULL);
- nfolders = HandleSize(srcfolders) / sizeof(FileType);
- (void) HandleLockHi(srcfolders);
- src = *srcfolders;
- }
- if (nfolders > 0) {
-
- /* get directory ID and strip file names */
- for (i = 0; i < nfolders; i++)
- FileDirID(&src[i]);
-
- /* select destination folder */
- if (! dst) {
- dst = &dstfolder;
- ResStrLen(RLS_SF, RLS_SF_FOLDER, prompt, sizeof(prompt));
- ResStrLen(RLS_FILE, RLS_FILE_UNTITLED_FOLDER, name, sizeof(name));
- FileSet(dst, -GetSFSaveDisk(), GetCurDirStore(), name);
- FileUnique(dst);
- strcpy(name, FileName(dst));
- FileSFPut(dst, prompt, name);
- }
-
- /* setup file merge data */
- memset(&catdata, 0, sizeof(ConcatenateData));
- catdata.dates = PtrBeginClear(sizeof(DateIndexType) * nfolders);
- catdata.nfolders = nfolders;
- catdata.dst = dst;
- catdata.src = src;
-
- /* create destination folder */
- check(! FolderExists(dst));
- FileClone(dst, &svdst);
- if (FileExists(dst))
- FileDelete(dst);
- FolderCreate(dst);
- FileDirID(dst);
- FileVerifySet(dst, true);
-
- /* merge folders */
- DoConcatenateTextFolders(&catdata);
-
- /* set modification time so finder will update its windows */
- FileCatalog(&svdst, &cat);
- GetDateTime(&cat.dirInfo.ioDrMdDat);
- FileCatalogSet(&svdst, &cat);
- }
- } CLEANUP {
- PtrEnd(catdata.dates);
- HandleEnd(srcfolders);
- } ENDTRY;
- }
-
- #ifdef NOT_USED
-
- /* could be used to support a drag-and-drop utility */
-
- /* concatenate the list of folders */
- static void HLEOpen(AEDescList *list, long n, void *data)
- {
- FileType file, *fp = &file;
- FileType **files = NULL;
- long nfiles = 0;
- long i = 0;
-
- TRY {
- for (i = 1; i <= n; i++) {
- AEGetNthFile(list, i, fp);
- if (FolderExists(fp)) { /* process only folders */
- if (! files)
- files = HandleBegin(0);
- HandleSizeSet(files, sizeof(FileType) * (nfiles + 1));
- (*files)[nfiles++] = *fp;
- }
- }
- if (files) {
- HandleLockHi(files);
- ConcatenateTextFolders(*files, nfiles, NULL);
- }
- } CLEANUP {
- HandleEnd(files);
- } ENDTRY;
- }
-
- #endif /* NOT_USED */
-